@create $root_class named "MCP 2.1 parser",parser
@prop parser."next_datakey" 9445 r
@prop parser."unquoted_string" "^[]a-zA-Z0-9-%~`!@#$^&()=+{}[|';?/><.,]+$" r __WIZARD__
;parser.("unique") = 0

@verb parser:"parse_mcp_alist" this none this
@program parser:parse_mcp_alist
"take args and return a list in the format:";
"{true if contains multiline, { { keyword-name, data, multiline }, ... }";
alist = {};
if (length(alist) % 2)
  raise(E_ARGS);
endif
contains_multiline = 0;
while (args)
  {keyword, value, @args} = args;
  if (keyword[$] != ":")
    raise(E_INVARG, "invalid keyword: " + keyword);
  else
    if (keyword[$ - 1] == "*")
      contains_multiline = 1;
      value = {};
      keyword = keyword[1..$ - 2];
    else
      keyword = keyword[1..$ - 1];
    endif
    alist = {@alist, {keyword, value}};
  endif
endwhile
return {contains_multiline, alist};
.

@verb parser:"parse_mcp" this none this
@program parser:parse_mcp
"parse_mcp(@args) =>";
"relies on argstr being a version of @args unwordified";
"{request-name, contains-multiline, authentication-key, data-tag, { { keyword-name, data }, ... } }";
if (length(args) < 1)
  raise(E_INVARG, "not enough arguments");
endif
request_name = args[1][4..$];
if (!request_name)
  raise(E_INVARG, "no request name");
endif
if (request_name == "*")
  return this:parse_mcp_continuation(@args[2..$]);
endif
"... if there is an authentication key, the length of args will be even ...";
if (length(args) % 2)
  authentication_key = E_NONE;
  message_args = args[2..$];
else
  authentication_key = args[2];
  message_args = args[3..$];
endif
{contains_multiline, alist} = this:parse_mcp_alist(@message_args);
if (contains_multiline)
  if (tag = $list_utils:iassoc("_data-tag", alist))
    "mulitline with a datatag, OK";
    data_tag = alist[tag][2];
    alist = listdelete(alist, tag);
  else
    raise(E_INVARG, "multiline fields with no data tag");
  endif
else
  data_tag = E_NONE;
endif
if (typeof(alist) == LIST)
  return {request_name, contains_multiline, authentication_key, data_tag, alist};
else
  return alist;
endif
.

@verb parser:"parse_mcp_continuation" this none this
@program parser:parse_mcp_continuation
{data_tag, keyword, @rest} = args;
value = argstr[(index(argstr, keyword) + length(keyword)) + 1..$];
keyword = keyword[1..$ - 1];
return {"*", data_tag, keyword, value};
.

@verb parser:"parse" this none this
@program parser:parse
"parse(@args) => parsed MCP message ready for dispatch or 0";
"                if there was nothing to dispatch for this message";
"                (as in multiline continuations, dispatch";
"                for those occurs at the END";
"returns {message, authkey, alist} or 0";
"argstr must equal the unmodified line from the client";
{argstr, @words} = args;
session = caller;
message = this:parse_mcp(@words);
if (message[1] == "*")
  {n, data_tag, keyword, value} = message;
  session:multiline_add_value(data_tag, keyword, value);
elseif ((message[1] == ":") || (message[1] == "END"))
  {request, dummy, data_tag, dummy, dummy} = message;
  return session:multiline_finish(player, data_tag);
else
  {request, contains_multiline, authkey, data_tag, alist} = message;
  if (contains_multiline)
    session:multiline_begin(request, authkey, data_tag, alist);
  else
    return {request, authkey, alist};
  endif
endif
return 0;
.

@verb parser:"unparse" this none this
@program parser:unparse
{request, authkey, alist} = args;
keyvals = "";
need_data_tag = 0;
multilines = {};
for keyval in (alist)
  {keyword, value, ?maybe_ignore} = keyval;
  if (typeof(value) == STR)
    if (!match(value, this.unquoted_string))
      value = toliteral(value);
    endif
  elseif (typeof(value) == LIST)
    need_data_tag = 1;
    multilines = {@multilines, {keyword, value}};
    keyword = keyword + "*";
    value = "\"\"";
  else
    value = toliteral(value);
  endif
  keyvals = (((keyvals + " ") + keyword) + ": ") + value;
endfor
if (need_data_tag)
  data_tag = this:next_datakey();
  keyvals = (keyvals + " _data-tag: ") + data_tag;
endif
message = "#$#" + request;
if (authkey)
  message = (message + " ") + authkey;
endif
message = {message + keyvals};
if (need_data_tag)
  prefix = ("#$#* " + data_tag) + " ";
  for field in (multilines)
    {keyword, value} = field;
    for line in (value)
      message = {@message, ((prefix + keyword) + ": ") + line};
    endfor
  endfor
  message = {@message, "#$#: " + data_tag};
endif
return message;
.

@verb parser:"next_datakey" this none this
@program parser:next_datakey
datakey = tostr(random(), this.next_datakey);
this.next_datakey = this.next_datakey + 1;
return datakey;
.

"***finished***

